package com.zz.common;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.zz.entity.SystemLog;
import com.zz.entity.User;
import com.zz.service.SystemLogService;
import com.zz.service.UserService;
import com.zz.utils.AesEncryptUtils;
import com.zz.utils.ByteUtils;
import com.zz.utils.Global;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Executor;

/**
 * 系统日志切面
 *
 * @author 朱彦燕
 *
 */
@Slf4j
@Aspect
@Component
public class SystemLogAspect {

    private ThreadLocal<SystemLog> sLogThreadLocal = new ThreadLocal<>();
    @Autowired
    private Executor customThreadPoolTaskExecutor;
    @Autowired
    private SystemLogService systemLogService;
    @Autowired
    private UserService userService;

    /**
     * 日志切点
     */
    @Pointcut("@annotation(com.zz.common.Log)")
    public void systemLogAspect() {
    }

    /**
     * 前置通知
     *
     * @param joinPoint
     * @throws Throwable
     */
    @Before(value = "systemLogAspect()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {
        HttpServletRequest request = ((ServletRequestAttributes) Objects
                .requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
        HttpServletResponse response = ((ServletRequestAttributes) Objects
                .requireNonNull(RequestContextHolder.getRequestAttributes())).getResponse();

        SystemLog systemLog = new SystemLog();

        systemLog.setStartTime(LocalDateTime.now());
        systemLog.setRequestUri(URLUtil.getPath(request.getRequestURI()));

        // 获取响应状态码
        int status = response.getStatus();
        /*
         * 200 正常
         * 400 未找到页面
         * 403 拒绝
         * 500 服务器错误
         * */
        if (status == 200){
            systemLog.setStatusCode(status+":"+"正常");
        } else if (status == 400){
            systemLog.setStatusCode(status+":"+"未找到页面");
        } else if (status == 403) {
            systemLog.setStatusCode(status+":"+"拒绝访问");
        } else if (status == 500) {
            systemLog.setStatusCode(status+":"+"服务器错误");
        }

        // 获取请求参数
        // 获取方法签名
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        // 获取方法
        Method method = signature.getMethod();
        // 获取方法上的注解
        Log annotation = method.getAnnotation(Log.class);

        // 获取注解上是否记录用户isUser的值
        boolean isUser = annotation.isUser();
        if (isUser == true){
//            String token = request.getHeader("token");
//            String userName = AesEncryptUtils.decrypt(token);
//            log.info("用户名=>>>{}",userName);
            String userName = (String) SecurityUtils.getSubject().getSession().getAttribute("user");
            // String userName = (String) request.getSession().getAttribute("user");
            log.info("日志执行中----->用户名：{}",userName);
            // 条件过滤器
            LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
            wrapper.eq(User::getName, userName);
            User user = userService.getOne(wrapper);
            // 设置用户名和编码
            systemLog.setCreateUserCode(user.getId());
            systemLog.setCreateUserName(userName);

        }else {
            systemLog.setCreateUserCode(0);
            systemLog.setCreateUserName("当前用户正在登录");
        }

        //获取注解的操作描述title
        String title = annotation.title();
        systemLog.setTitle(title);

        // 获取操作描述的属性值
        String dataLocationType = annotation.dataLocationType();
        String type1 = "body";
        String type2 = "link";
        if (dataLocationType.equals(type1)){
            Object[] args = joinPoint.getArgs();
            Object arg = args[0];
            String params = JSONObject.toJSONString(arg);
            systemLog.setRequestParams(params);
        }else if(dataLocationType.equals(type2)){
            Map<String, String[]> parameterMap = request.getParameterMap();
            systemLog.setRequestParams(formatParams(parameterMap));
        }else {
            systemLog.setRequestParams("没有参数");
        }

        systemLog.setRequestMethod(request.getMethod());
        systemLog.setRequestIp(ServletUtil.getClientIP(request));
        systemLog.setServerAddress(request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort());
        String userAgentStr = request.getHeader("User-Agent");
        systemLog.setUserAgent(userAgentStr);
        UserAgent userAgent = UserAgentUtil.parse(userAgentStr);
        systemLog.setDeviceName(userAgent.getOs().getName());
        systemLog.setBrowserName(userAgent.getBrowser().getName());

        sLogThreadLocal.set(systemLog);

        log.info("开始计时: {}  URI: {}  IP: {}", systemLog.getStartTime(), systemLog.getRequestUri(), systemLog.getRequestIp());
    }

    /**
     * 返回通知
     *
     * @param ret
     */
    @AfterReturning(pointcut = "systemLogAspect()", returning = "ret")
    public void doAfterReturning(Object ret) {
        SystemLog systemLog = sLogThreadLocal.get();
        systemLog.setLogType(Global.LOG_INGO);
        systemLog.setEndTime(LocalDateTime.now());
        systemLog.setExecuteTime(Long.valueOf(ChronoUnit.MILLIS.between(systemLog.getStartTime(), systemLog.getEndTime())));
        R<?> r = Convert.convert(R.class, ret);
        if (!r.getCode().equals(Global.TRUE)) {
            systemLog.setIsException(Global.NO);
            // log.info("没有产生异常=>>>异常信息:{}",r.getMsg());
        } else {
            systemLog.setIsException(Global.YES);
            systemLog.setExceptionInfo(r.getMsg());
        }
        customThreadPoolTaskExecutor.execute(new SaveLogThread(systemLog, systemLogService));
        sLogThreadLocal.remove();

        Runtime runtime = Runtime.getRuntime();
        log.info("计时结束: {}  用时: {}ms  URI: {}  总内存: {}  已用内存: {}", systemLog.getEndTime(), systemLog.getExecuteTime(),
                systemLog.getRequestUri(), ByteUtils.formatByteSize(runtime.totalMemory()),
                ByteUtils.formatByteSize(runtime.totalMemory() - runtime.freeMemory()));
    }

    /**
     * 异常通知
     *
     * @param e
     */
    @AfterThrowing(pointcut = "systemLogAspect()", throwing = "e")
    public void doAfterThrowable(Throwable e) {
        SystemLog systemLog = sLogThreadLocal.get();
        systemLog.setLogType(Global.LOG_ERROR);
        systemLog.setEndTime(LocalDateTime.now());
        systemLog.setExecuteTime(Long.valueOf(ChronoUnit.MINUTES.between(systemLog.getStartTime(), systemLog.getEndTime())));
        systemLog.setIsException(Global.YES);
        systemLog.setExceptionInfo(e.getMessage());
        customThreadPoolTaskExecutor.execute(new SaveLogThread(systemLog, systemLogService));
        sLogThreadLocal.remove();

        Runtime runtime = Runtime.getRuntime();
        log.info("计时结束: {}  用时: {}ms  URI: {}  总内存: {}  已用内存: {}", systemLog.getEndTime(), systemLog.getExecuteTime(),
                systemLog.getRequestUri(), ByteUtils.formatByteSize(runtime.totalMemory()),
                ByteUtils.formatByteSize(runtime.totalMemory() - runtime.freeMemory()));
    }

    /**
     * 格式化参数
     *
     * @param parameterMap
     * @return
     */
    private String formatParams(Map<String, String[]> parameterMap) {
        if (parameterMap == null) {
            return null;
        }
        StringBuilder params = new StringBuilder();
        for (Map.Entry<String, String[]> param : (parameterMap).entrySet()) {
            if (params.length() != 0) {
                params.append("&");
            }
            params.append(param.getKey() + "=");
            if (StrUtil.endWithIgnoreCase(param.getKey(), "password")) {
                params.append("*");
            } else if (param.getValue() != null) {
                params.append(ArrayUtil.join(param.getValue(), ","));
            }
        }
        return params.toString();
    }

    /**
     * 保存日志线程
     *
     * @author 朱彦燕
     *
     */
    private static class SaveLogThread extends Thread {
        private SystemLog systemLog;
        private SystemLogService systemLogService;

        public SaveLogThread(SystemLog systemLog, SystemLogService systemLogService) {
            this.systemLog = systemLog;
            this.systemLogService = systemLogService;
        }

        @Override
        public void run() {
            systemLog.setCreateDate(LocalDateTime.now());
            systemLogService.save(systemLog);
        }
    }
}
